package com.funcourse.demo.business;

import com.funcourse.demo.api.restful.io.response.ApiResponse;
import com.funcourse.demo.api.restful.io.response.busi.CompeteRoomHttpVO;
import com.funcourse.demo.api.restful.io.response.error.CommonUsedError;
import com.funcourse.demo.api.restful.security.AuthUserHolder;
import com.funcourse.demo.api.websocket.SocketServer;
import com.funcourse.demo.business.oss.ObjectReferenceManager;
import com.funcourse.demo.model.CompeteRoom;
import com.funcourse.demo.model.CompeteRoom.RoomStatus;
import com.funcourse.demo.model.CompeteRoom.RoomType;
import com.funcourse.demo.model.User;
import com.funcourse.demo.model.UserWithScore;
import com.funcourse.demo.model.repo.CompeteRoomRepo;
import com.funcourse.demo.model.repo.room.AttenderRoomRelationRepo;
import com.funcourse.demo.model.repo.room.RoomNumberRepo;
import com.funcourse.demo.model.room.AttenderRoomRelation;
import com.funcourse.demo.model.room.RoomNumber;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

@Slf4j
@Service
public class CompeteRoomService {

  @Autowired
  CompeteRoomRepo competeRoomRepo;

  @Autowired
  RoomCacher roomCacher;

  @Autowired
  RoomNumberRepo roomNumberRepo;

  @Autowired
  AttenderRoomRelationRepo attenderRoomRelationRepo;

  @Autowired
  SocketServer socketServer;

  @Autowired
  AuthUserHolder authUserHolder;

  RedisResourceLock redisResourceLock;

  @Autowired
  StaticsService staticsService;

  @Autowired
  RedisTemplate redisTemplate;

  @Autowired
  ObjectReferenceManager objectReferenceManager;

  BlockingQueue<String> roomNotifyQueue = new LinkedBlockingDeque();

  private static final String ROOM_LOCK_KEY_PREFIX = "funcourse.room.lock.";

  @Scheduled(fixedDelay = 800,initialDelay = 3000)
  public void sendRoomNotifyMessage(){
    Set<String> toNotifyRoomSet = new HashSet<>();
    roomNotifyQueue.drainTo(toNotifyRoomSet);
    toNotifyRoomSet.forEach(roomId->{
        socketServer.sendRoomUpdateMessage(roomId);
    });
  }

    @Scheduled(fixedDelay = 1000,initialDelay = 3000)
  public void closedCollectionRooms(){
    int page=0;
    final int size=100;
    while (true){
      Page<CompeteRoom> deadRooms = competeRoomRepo
          .findByRoomStatusAndRoomTypeAndDeadLineLessThan(RoomStatus.STARTED,RoomType.COLLECT,System.currentTimeMillis(), PageRequest.of(page, size));
      List<CompeteRoom> rooms = deadRooms.getContent();
      rooms.forEach(competeRoom -> {
        competeRoom.setRoomStatus(RoomStatus.CLOSED);
        staticsService.doStatics(competeRoom);
      });
      competeRoomRepo.saveAll(rooms);
      if(page<deadRooms.getTotalPages()){
        page++;
      }else{
        break;
      }
    }
  }

  @Scheduled(fixedDelay = 4000,initialDelay = 3000)
  public void startCollectionRooms(){
    int page=0;
    final int size=100;
    while (true){
      Page<CompeteRoom> toStartRooms = competeRoomRepo
              .findByRoomStatusAndRoomTypeAndStartTimeLessThan(RoomStatus.CREATED,RoomType.COLLECT,System.currentTimeMillis()+5000, PageRequest.of(page, size));
      List<CompeteRoom> rooms = toStartRooms.getContent();
      rooms.forEach(competeRoom -> {
        competeRoom.setRoomStatus(RoomStatus.STARTED);
      });
      competeRoomRepo.saveAll(rooms);
      if(page<toStartRooms.getTotalPages()){
        page++;
      }else{
        break;
      }
    }
  }

  private void sortUserWithScoreList(List<UserWithScore> userWithScores){
    userWithScores.sort((u1,u2)-> u2.getTotalScore() - u1.getTotalScore());
    for (int i = 0; i < userWithScores.size(); i++) {
      userWithScores.get(i).setRanking(i+1);
    }
  }

  public ApiResponse<CompeteRoom> viewRoomInfoByNo(String roomNo){
    List<RoomNumber> byRoomNo = roomNumberRepo.findByRoomNo(roomNo);
    if(CollectionUtils.isEmpty(byRoomNo)){
      return new ApiResponse(RoomServiceError.ROOM_NOT_FOUND,null);
    }
    CompeteRoom competeRoom = roomCacher.getRoomById(byRoomNo.get(0).getRoomId());
    if (null != competeRoom) {
      User authUser = authUserHolder.getAuthUser();
      return new ApiResponse(CommonUsedError.SUCCESS, new CompeteRoomHttpVO(competeRoom,authUser.getId(),5,true));
    }
    return new ApiResponse(CommonUsedError.BAD_REQUEST.customizedError("no room found"), null);
  }

  public ApiResponse<CompeteRoom> joinRoom(String roomId){
    final long methodStartTimeMillis = System.currentTimeMillis();
    User authUser = authUserHolder.getAuthUser();
    log.debug("time1 is {}",System.currentTimeMillis() - methodStartTimeMillis);
    redisResourceLock.tryToGetResourceLockBlocking(roomId);
    log.debug("time2 is {}",System.currentTimeMillis() - methodStartTimeMillis);
    CompeteRoom competeRoom = roomCacher.getRoomById(roomId);
    log.debug("time3 is {}",System.currentTimeMillis() - methodStartTimeMillis);
    if (null == competeRoom) {
      redisResourceLock.releaseResourceLock(roomId);
      log.debug("time31 is {}",System.currentTimeMillis() - methodStartTimeMillis);
      return new ApiResponse(CommonUsedError.BAD_REQUEST.customizedError("no room found"), null);
    }
    List<UserWithScore> userWithScoreList = competeRoom.getUserWithScoreList();
    final UserWithScore userWithScore = new UserWithScore(authUser);
    boolean userRejoin = userWithScoreList.contains(userWithScore);
    log.debug("time4 is {}",System.currentTimeMillis() - methodStartTimeMillis);
    if(RoomStatus.CLOSED == competeRoom.getRoomStatus()){
      redisResourceLock.releaseResourceLock(roomId);
      log.debug("time41 is {}",System.currentTimeMillis() - methodStartTimeMillis);
      return new ApiResponse(RoomServiceError.ROOM_STATUS_WRONG, null);
    }
    if(userRejoin){
      redisResourceLock.releaseResourceLock(roomId);
      log.debug("time42 is {}",System.currentTimeMillis() - methodStartTimeMillis);
      //reduce net work load
      competeRoom.setUserWithScoreList(new ArrayList<>());
      return new ApiResponse(CommonUsedError.SUCCESS, competeRoom);
    }
    if(competeRoom.getRoomStatus() == RoomStatus.CREATED){
      userWithScoreList.add(0,userWithScore);
    }else{
      userWithScoreList.add(userWithScore);
    }
    log.debug("time8 is {}",System.currentTimeMillis() - methodStartTimeMillis);
    //to persist when finish
    roomCacher.save(competeRoom);
    log.debug("time9 is {}",System.currentTimeMillis() - methodStartTimeMillis);
    try {
      roomNotifyQueue.put(roomId);
    } catch (InterruptedException e) {
      log.warn("putting roomId to notify queue failed {}",roomId);
    }
  //to save attenderRoomRelations when finish if realTime room;
    if(RoomType.COLLECT == competeRoom.getRoomType()){
      attenderRoomRelationRepo.save(new AttenderRoomRelation(null, authUser.getId(), competeRoom.getId(),
          System.currentTimeMillis()));
    }
    redisResourceLock.releaseResourceLock(roomId);
    log.debug("time10 is {}",System.currentTimeMillis() - methodStartTimeMillis);
    //reduce net work load
    competeRoom.setUserWithScoreList(new ArrayList<>());
    return new ApiResponse(CommonUsedError.SUCCESS, competeRoom);
  }

  public ApiResponse submitMyAnswer(String roomId, UserWithScore.Score score) {
    User authUser = authUserHolder.getAuthUser();
    redisResourceLock.tryToGetResourceLockBlocking(roomId);
    CompeteRoom competeRoom = roomCacher.getRoomById(roomId);
    if (null == competeRoom) {
      redisResourceLock.releaseResourceLock(roomId);
      return new ApiResponse(CommonUsedError.BAD_REQUEST.customizedError("no room found"), null);
    }
    if(RoomStatus.STARTED != competeRoom.getRoomStatus()){
      redisResourceLock.releaseResourceLock(roomId);
      return new ApiResponse(RoomServiceError.ROOM_STATUS_WRONG.customizedError("room is not started"), null);
    }
    List<UserWithScore> userWithScoreList = competeRoom.getUserWithScoreList();
    if(CollectionUtils.isEmpty(userWithScoreList)){
      userWithScoreList = new ArrayList<>();
      competeRoom.setUserWithScoreList(userWithScoreList);
    }

    Boolean userFound = false;
    Boolean answerDuplicateSubmit = false;
    for(UserWithScore userWithScore:userWithScoreList){
      if (userWithScore.getId().equals(authUser.getId())) {
        //重新答题将不计入
        if(!userWithScore.addSocre(score)){
          answerDuplicateSubmit = true;
          log.warn("user submit answer duplicate");
        }
        userFound = true;
        break;
      }
    }
    if(!userFound){
      redisResourceLock.releaseResourceLock(roomId);
      return new ApiResponse(CommonUsedError.NOT_FOUND.customizedError("user is not a member of this room"), null);
    }
    if(answerDuplicateSubmit){
      redisResourceLock.releaseResourceLock(roomId);
      return new ApiResponse(CommonUsedError.SUCCESS, null);
    }

    sortUserWithScoreList(userWithScoreList);
    UserWithScore userPointer;
    Boolean allUsersSubmitAnswer = true;
    for (int i = 0; i < userWithScoreList.size(); i++) {
      userPointer = userWithScoreList.get(i);
      if(!competeRoom.getCurrentQuestionNO().equals(userPointer.getMyOnGoingQuestionNumber())){
        allUsersSubmitAnswer = false;
      }
    }
    competeRoom.setQuestionAllAnswered(allUsersSubmitAnswer);
    roomCacher.save(competeRoom);
    if(competeRoom.getRoomType() == RoomType.REAL_TIME){
      try {
        roomNotifyQueue.put(roomId);
      } catch (InterruptedException e) {
        log.warn("putting roomId to notify queue failed {}",roomId);
      }
    }
    redisResourceLock.releaseResourceLock(roomId);
    return new ApiResponse(CommonUsedError.SUCCESS, null);
  }

  public ApiResponse leaveRoom(String roomId){
    User authUser = authUserHolder.getAuthUser();
    redisResourceLock.tryToGetResourceLockBlocking(roomId);
    CompeteRoom competeRoom = roomCacher.getRoomById(roomId);
    if (null == competeRoom) {
      redisResourceLock.releaseResourceLock(roomId);
      return new ApiResponse(CommonUsedError.BAD_REQUEST.customizedError("no room found"), null);
    }
    if(RoomStatus.CLOSED == competeRoom.getRoomStatus()){
      redisResourceLock.releaseResourceLock(roomId);
      return new ApiResponse(RoomServiceError.ROOM_STATUS_WRONG.customizedError("room closed already"), null);
    }
    if(RoomStatus.STARTED == competeRoom.getRoomStatus()){
      redisResourceLock.releaseResourceLock(roomId);
      //已经开始的房间再退出算中途退出，不记录
      return new ApiResponse(CommonUsedError.SUCCESS, null);
    }
    List<UserWithScore> userWithScoreList = competeRoom.getUserWithScoreList();
    final UserWithScore userWithScore = new UserWithScore(authUser);
    if(!userWithScoreList.contains(userWithScore)){
      redisResourceLock.releaseResourceLock(roomId);
      return new ApiResponse(CommonUsedError.SUCCESS, null);
    }
    userWithScoreList.remove(userWithScore);
//    userWithScoreList.sort(Comparator.comparing(UserWithScore::getTotalScore));
//    for (int i = 0; i < userWithScoreList.size(); i++) {
//      userWithScoreList.get(i).setRanking(userWithScoreList.size() - i);
//    }
    roomCacher.save(competeRoom);
    try {
      roomNotifyQueue.put(roomId);
    } catch (InterruptedException e) {
      log.warn("putting roomId to notify queue failed {}",roomId);
    }
    attenderRoomRelationRepo.deleteByAttenderIdAndRoomId(authUser.getId(),roomId);
    redisResourceLock.releaseResourceLock(roomId);
    return new ApiResponse(CommonUsedError.SUCCESS, null);
  }

  public ApiResponse closeRealTimeRoom(String competeRoomId){
    return closeRealTimeRoom(competeRoomId,true);
  }

  public ApiResponse closeRealTimeRoom(String competeRoomId,Boolean needAuth){
    CompeteRoom competeRoom = roomCacher.getRoomById(competeRoomId);
    if (null == competeRoom) {
      return new ApiResponse(CommonUsedError.BAD_REQUEST, "");
    }
    if(RoomStatus.CLOSED == competeRoom.getRoomStatus()){
      return new ApiResponse(RoomServiceError.ROOM_STATUS_WRONG, "房间已关闭");
    }
    if(needAuth){
      User authUser = authUserHolder.getAuthUser();
      if (!authUser.getId().equals(competeRoom.getOwnerId())) {
        return new ApiResponse(CommonUsedError.UNAUTHORIZED, "");
      }
    }
    if(RoomStatus.CREATED == competeRoom.getRoomStatus()){
      competeRoomRepo.deleteById(competeRoomId);
      attenderRoomRelationRepo.deleteByRoomId(competeRoomId);
      CompletableFuture.runAsync(()->{
        objectReferenceManager.referenceCountChangeByQuizCreate(competeRoom.getQuiz());
      });
      roomCacher.clear(competeRoomId);
      socketServer.sendRoomStatusChangeMsg(competeRoomId,RoomStatus.CLOSED);
    }else{
      CompletableFuture.runAsync(()->{
        final CompeteRoom persist = roomCacher.persist(competeRoomId,true);
        socketServer.sendRoomStatusChangeMsg(competeRoomId,RoomStatus.CLOSED);
        staticsService.doStatics(persist);
        saveAttenderRecords(persist);
      });
    }
    roomNumberRepo.deleteByRoomId(competeRoomId);
    return new ApiResponse(CommonUsedError.SUCCESS, "");
  }
  private void saveAttenderRecords(CompeteRoom competeRoom){
    final List<UserWithScore> userWithScoreList = competeRoom.getUserWithScoreList();
    attenderRoomRelationRepo.saveAll(userWithScoreList.stream().map(
        userWithScore -> new AttenderRoomRelation(null, userWithScore.getId(), competeRoom.getId(),
            competeRoom.getOrgnizedTime())).collect(Collectors.toList()));
  }

  @PostConstruct
  public void initRedisResourceLock(){
    redisResourceLock = new RedisResourceLock(ROOM_LOCK_KEY_PREFIX,redisTemplate);
  }

}
